﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using élénkPróbája.helpers;
using élénkPróbája.results;
using élénkPróbája.validators;

namespace élénkPróbája.native.rule
{
    public class LitPropRuleCollection<TProperty> : PropRule
    {
        public LitPropRuleCollection(MemberInfo member, Func<object, object> propertyFunc, LambdaExpression expression, Func<CascadeMode> cascadeModeThunk, Type typeToValidate, Type containerType) : base(member, propertyFunc, expression, cascadeModeThunk, typeToValidate, containerType) {
        }

        /// <summary>
        /// Creates a new property rule from a lambda expression.
        /// </summary>
        public new static LitPropRuleCollection<TProperty> Create<T>(Expression<Func<T, IEnumerable<TProperty>>> expression, Func<CascadeMode> cascadeModeThunk)
        {
            var member = expression.GetMember();
            var compiled = expression.Compile();

            return new LitPropRuleCollection<TProperty>(member, compiled.CoerceToNonGeneric(), expression, cascadeModeThunk, typeof(TProperty), typeof(T));
        }

        protected override Task<IEnumerable<ValidationFailure>> InvokePropertyValidatorAsync(ValidationContext context, ILitPropertyValidator validator, string propertyName, CancellationToken cancellation)
        {

            var propertyContext = new PropertyValidatorContext(context, this, propertyName);
            var results = new List<ValidationFailure>();
            var delegatingValidator = validator as ILitDelegatingValidator;

            if (delegatingValidator == null || delegatingValidator.CheckCondition(propertyContext.Instance))
            {
                var collectionPropertyValue = propertyContext.PropertyValue as IEnumerable<TProperty>;

                if (collectionPropertyValue != null)
                {

                    var validators = collectionPropertyValue.Select((v, count) => {
                        var newContext = context.CloneForChildValidator(context.InstanceToValidate);
                        newContext.PropChain.Add(propertyName);
                        newContext.PropChain.AddIndexer(count);

                        var newPropertyContext = new PropertyValidatorContext(newContext, this, newContext.PropChain.ToString(), v);

                        return validator.ValidateAsync(newPropertyContext, cancellation)
                            .Then(fs => results.AddRange(fs));
                    });


                    return
                    TaskHelpers.Iterate(
                        validators,
                        cancellationToken: cancellation
                    ).Then(() => results.AsEnumerable(), runSynchronously: true);
                }
            }

            return TaskHelpers.FromResult(Enumerable.Empty<ValidationFailure>());
        }

        protected override IEnumerable<ValidationFailure> InvokePropertyValidator(ValidationContext context, ILitPropertyValidator validator, string propertyName)
        {
            var propertyContext = new PropertyValidatorContext(context, this, propertyName);
            var results = new List<ValidationFailure>();
            var delegatingValidator = validator as ILitDelegatingValidator;
            if (delegatingValidator == null || delegatingValidator.CheckCondition(propertyContext.Instance))
            {
                var collectionPropertyValue = propertyContext.PropertyValue as IEnumerable<TProperty>;

                int count = 0;

                if (collectionPropertyValue != null)
                {
                    foreach (var element in collectionPropertyValue)
                    {
                        var newContext = context.CloneForChildValidator(context.InstanceToValidate);
                        newContext.PropChain.Add(propertyName);
                        newContext.PropChain.AddIndexer(count++);

                        var newPropertyContext = new PropertyValidatorContext(newContext, this, newContext.PropChain.ToString(), element);

                        results.AddRange(validator.Validate(newPropertyContext));
                    }
                }
            }
            return results;
        }
    }
}